﻿using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;

namespace GShare1._0.Server
{
    public class Download
    {
        public Client Client;
        public bool Alive;
        public int Size = 0;
        private bool _overwrite = false;
        public float Percent = 0;
        private Socket _socket;

        public Socket Socket
        {
            get { return _socket; }
            set { _socket = value; }
        }

        public delegate void FinishEvent(object sender,EventArgs e);
        public event FinishEvent Finish;
        public MainThreadInvoker MainInvoker;
        public string Filename;
        private string username;
        public GUI_FileDownload DownloadForm;
        private FileManager fileManager = new FileManager();
        private Stopwatch timer = new Stopwatch();
        private double _timeLastCheck = 0;
        Timer ticker = new Timer();



        public Download(string username,MainThreadInvoker invoker,string filename)
        {
            this.Filename = filename;
            this.username = username;
            MainInvoker = invoker;
            ticker.Tick += ticker_Tick;
            
            
        }

        /// <summary>
        /// Starts the Connection Process For download object - Must be called using the Main UI Thread (GUI for Download created here)
        /// </summary>
        public void BeginConnection(Socket socket)
        {
            try
            {
                this._socket = socket;
                //Create Form And Events for download
                DownloadForm = new GUI_FileDownload("Download Complete from " + username + " of " + Size + "bytes","File Download: " + username);
                DownloadForm.Show();

                DownloadStart(socket);
            }
            catch (Exception ex)
            {
                
                MessageBox.Show(ex.Message + ex.TargetSite);
                DisconnectDownloadSocket();
                throw;
            }
            

            
        }

        private void download_Finish(object sender, EventArgs e)
        {
            Download downloadObject = (Download)sender;
            //when the download is done call the update method of the downloadform the finnaly update
            MainInvoker.BeginInvoke((MethodInvoker)delegate
            {
                if (downloadObject.DownloadForm.Visible)
                {
                    downloadObject.DownloadForm.UpdateUI(downloadObject.Percent, downloadObject.Size, downloadObject.Alive, downloadObject.username);
                    downloadObject.DownloadForm.Close();
                    MessageBox.Show("Complete Upload of: " + downloadObject.Size + "bytes from user: " + downloadObject.username);
                }

            },new AsyncCallback(FinishUpdateUi),null);
            
        }

        /// <summary>
        /// called when the updating of the ui is complete
        /// </summary>
        /// <param name="ar"></param>
        private void FinishUpdateUi(IAsyncResult ar)
        {
            Finish.BeginInvoke(this, new EventArgs(), null, null);
        }

        void ticker_Tick(object sender, EventArgs e)
        {
            try
            {
                DownloadForm.UpdateUI(Percent, Size, Alive, username);
            }
            catch (Exception ex)
            {
                
                MessageBox.Show(ex.Message + ex.TargetSite);
            }
            
        }

        /// <summary>
        /// Starts the Download from the client
        /// Creates GUI_FileUpload
        /// </summary>
        public void DownloadStart(Socket socket)
        {

            try
            {
                Alive = true;
                fileManager.CheckContainingFolder();

                
                //Debug.WriteLine(ticker.Interval.ToString());



                ServerState state = new ServerState();
                state.Client = socket;
                //issue first receive

                this._socket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None,
                new AsyncCallback(ClientDownloadCallback), state);

                //Create Events for the form - Used for when closing
                DownloadForm.FormClosing += downloadForm_FormClosing;
            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.Message + ex.TargetSite);
            }
            
            

            //Create UI for download progress
        }

        void downloadForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            //GUI_FileDownload form = (GUI_FileDownload)sender;
            Alive = false; // Stop The Download
            //this._client.autoEvent.Set(); //restart the ClientConnection Thread
            if (_socket.Connected)
            {
                DisconnectDownloadSocket();
            }
            
        }


        //Disconnects the Client and Server Connection needed for the Download - Finish Event is called here after everything
        private void DisconnectDownloadSocket()
        {
            try
            {
                _socket.Close();
 
                //call Finish on the download because the connection is now being ended
                //Finish.BeginInvoke(this,new EventArgs(),null, null);
                download_Finish(this, new EventArgs());
                //socket = null;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.TargetSite + ex.Message);
                download_Finish(this, new EventArgs());
            }
                
        }

        public void ClientDownloadCallback(IAsyncResult ar)
        {
            try
            {
                _socket.ReceiveTimeout = 5000;
                //Update the UI
            //downloadForm.UpdateUI();
            ServerState state = (ServerState)ar.AsyncState;
            SocketError socketError;

            int dataRead = _socket.EndReceive(ar, out socketError);
            int dataOffset = 0; //Simplify logic
            //Array.Resize(ref state.Buffer, dataRead);

            //check if download was canceld externaly
            if (Alive == false)
            {
                //Debug.WriteLine("Download Stopped External");
                ticker.Stop();

                DisconnectDownloadSocket();
                return;
            }
            //if the socket error is found close the socket and return/exit from the async loop
            if (socketError != SocketError.Success)
            {
                Debug.WriteLine("SocketError.NOTSUCCESS");
                ticker.Stop();

                Alive = false;
                DisconnectDownloadSocket();
                return;
            }

            
            
            if (dataRead == 9 || dataRead <= 0)
            {
                //check if still connected
                byte[] arr = state.Buffer;
                Array.Resize(ref arr, dataRead);
                if (Encoding.ASCII.GetString(arr) == "ENDUPLOAD")
                {  
                    ticker.Stop();

                    MessageBox.Show("Upload Ended By User");
                    //_client.autoEvent.Set();//if upload is stopped just continue with client connection thread

                    DisconnectDownloadSocket();
                    return;
                }
                MessageBox.Show("Upload Ended - Error In Connection");
                DisconnectDownloadSocket();
                
            }

            if (!state.DataSizeReceived)
            {
                if (dataRead >= 8)
                {

                    //Download Starts Here
                    timer.Start();
                    ticker.Enabled = true;
                    ticker.Start();
                    ticker.Interval = 1000;

                    //we received data size prefix
                    state.DataSize = BitConverter.ToInt32(state.Buffer, 0);
                    state.DataSizeReceived = true;
                    dataRead -= 8;
                    dataOffset += 8;
                    Size = state.DataSize;
                }
            }

            if ((state.DataReceived + dataRead) == state.DataSize)
            {
                //we have all the data
                state.DataReceived += dataRead;
                //condition if the file does exists and has not started writing to the file
                if (File.Exists(fileManager.UploadsPath + "\\" + Filename) && !_overwrite)
                {
                    using (var stream = new FileStream(fileManager.UploadsPath + "\\" + Filename, FileMode.Create))
                    {
                        stream.Write(state.Buffer, dataOffset, dataRead);
                    }
                    _overwrite = true;
                }
                else
                {
                    using (var stream = new FileStream(fileManager.UploadsPath + "\\" + Filename, FileMode.Append))
                    {
                        stream.Write(state.Buffer, dataOffset, dataRead);
                    }
                }

                Percent = ((float)state.DataReceived / (float)state.DataSize) * 100.0f;
                timer.Stop();

                Debug.WriteLine("Data received. Size: " + state.DataSize.ToString());
                //ticker_Tick(this, new EventArgs());
                
                ticker.Stop();
                //_client.autoEvent.Set();
                DisconnectDownloadSocket();
                return;
                

            }
            else
            {
                //there is still data pending, store what we've
                //received and issue another BeginReceive
                //state.Data.Write(state.Buffer, dataOffset, dataRead);
                state.DataReceived += dataRead;

                //check if the file does not exist
                if (!File.Exists(fileManager.UploadsPath + "\\" + Filename))
                {
                    using (var stream = new FileStream(fileManager.UploadsPath + "\\" + Filename, FileMode.CreateNew))
                    {
                        stream.Write(state.Buffer,dataOffset, dataRead);
                    }
                }
                //condition if the file does exists and has not started writing to the file
                if (File.Exists(fileManager.UploadsPath + "\\" + Filename) && !_overwrite)
                {
                    using (var stream = new FileStream(fileManager.UploadsPath + "\\" + Filename, FileMode.Create))
                    {
                        
                        stream.Write(state.Buffer,dataOffset, dataRead);
                    }
                    _overwrite = true;
                }
                else
                {

                    using (var stream = new FileStream(fileManager.UploadsPath + "\\" + Filename, FileMode.Append))
                    {
                        stream.Write(state.Buffer, dataOffset, dataRead);
                    }
                }
                
                

                Percent = ((float)state.DataReceived / (float)Size) * 100.0f;
                //Debug.WriteLine(percent);
                if (state.DataReceived > Size)
                {
                    Array.Resize(ref state.Buffer, dataRead);
                    string str = Encoding.ASCII.GetString(state.Buffer);
                    MessageBox.Show("Error ");
                    
                    //this._client.autoEvent.Set();
                    Alive = false;
                    DisconnectDownloadSocket();
                    return;
                }
                //check if this is the whole file
                if (state.DataReceived == Size)
                {
                    //this._client.autoEvent.Set();
                    this.ticker.Stop();
                    //ticker_Tick(this, new EventArgs());
                    //this.downloadForm.UpdateUI(percent, size, _client.clientInfo.UserName);
                    Alive = false;
                    DisconnectDownloadSocket();
                    return;
                }


                if (timer.Elapsed.TotalSeconds > _timeLastCheck)
                {
                    ticker_Tick(this,new EventArgs());
                    _timeLastCheck = timer.Elapsed.TotalSeconds;
                }

                if (Alive)
                {
                    _socket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, new AsyncCallback(ClientDownloadCallback), state);
                }
                
            }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + " " + ex.TargetSite.ToString());
                //this._client.autoEvent.Set();
                this.ticker.Stop();
                Alive = false;
                DisconnectDownloadSocket();
                return;
            }
            
            }
        }
    }
